/*
 * fv3tk
 * Copyright (C) 2009  John Pritchard, jdp@syntelos.org
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package fv3tk.hid;

import fv3tk.Fv3Hid;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * Example / placeholder to be deleted on upgrade.
 * 
 */
public class Logitech 
    extends fv3tk.hid.File
    implements Pad, Button, Stick
{
    /**
     * A logitech pad button 
     */
    public static enum Pad
        implements fv3tk.hid.Pad
    {

        Top   (0x000,PadTop),
        Right (0x200,PadRight),
        Bottom(0x400,PadBottom),
        Left  (0x600,PadLeft);

        public final int lvalue, rvalue;


        Pad(int lvalue, int rvalue){
            this.lvalue = lvalue;
            this.rvalue = rvalue;
        }


        public boolean isPad(){
            return true;
        }


        public static Pad For(int lvalue){
            switch (lvalue){
            case 0x000:
                return Pad.Top;
            case 0x200:
                return Pad.Right;
            case 0x400:
                return Pad.Bottom;
            case 0x600:
                return Pad.Left;
            default:
                return null;
            }
        }

    }
    /**
     * A logitech (not pad) button 
     */
    public static enum Button
        implements fv3tk.hid.Button
    {

        B1 (0x1800,Button1),
        B2 (0x2800,Button2),
        B3 (0x4800,Button3),
        B4 (0x8800,Button4),
        B5 (0x0801,Button5),
        B6 (0x0802,Button6),
        B7 (0x0804,Button7),
        B8 (0x0808,Button8),
        B9 (0x0810,Button9),
        B10(0x0820,Button10);

        public final int lvalue, rvalue;


        Button(int lvalue, int rvalue){
            this.lvalue = lvalue;
            this.rvalue = rvalue;
        }


        public boolean isButton(){
            return true;
        }



        public static Button For(int lvalue){
            switch (lvalue){
            case 0x1800:
                return Button.B1;
            case 0x2800:
                return Button.B2;
            case 0x4800:
                return Button.B3;
            case 0x8800:
                return Button.B4;
            case 0x0801:
                return Button.B5;
            case 0x0802:
                return Button.B6;
            case 0x0804:
                return Button.B7;
            case 0x0808:
                return Button.B8;
            case 0x0810:
                return Button.B9;
            case 0x0820:
                return Button.B10;
            default:
                return null;
            }
        }

    }
    /**
     * A logitech joystick 
     */
    public static class Stick
        extends Object
        implements fv3tk.hid.Stick
    {
        public final static int MIN = Byte.MIN_VALUE;
        public final static int MAX = Byte.MAX_VALUE;

        public final boolean left;

        public final int rvalue;

        public final byte minX, minY, maxX, maxY;

        private volatile String string;

        protected volatile float normX, normY;


        Stick(int leftA, int leftB, int rightA, int rightB){
            super();
            int a, b;

            if (0x8080 == rightA){
                this.left = true;
                this.rvalue = StickLeft;
                a = leftA;
                b = leftB;
            }
            else if (0x8080 == leftB){
                this.left = false;
                this.rvalue = StickRight;
                a = rightA;
                b = rightB;
            }
            else
                throw new IllegalArgumentException();

            {
                this.minX = (byte)((a >> 8) & 0xff);
                this.maxX = (byte)((a >> 0) & 0xff);
                this.minY = (byte)((b >> 8) & 0xff);
                this.maxY = (byte)((b >> 0) & 0xff);
            }
        }


        public boolean isStick(){
            return true;
        }
        public String name(){
            if (this.left)
                return "Left";
            else
                return "Right";
        }
        public int ordinal(){
            if (this.left)
                return 0;
            else
                return 1;
        }
        public byte getMinX(){
            return this.minX;
        }
        public byte getMinY(){
            return this.minY;
        }
        public byte getMaxX(){
            return this.maxX;
        }
        public byte getMaxY(){
            return this.maxY;
        }
        public float getX(){
            float x = this.normX;
            if (0f == x){
                float max = this.maxX;
                float min = this.minX;
                x = (1f / (max - min));
                this.normX = x;
            }
            return x;
        }
        public float getY(){
            float y = this.normY;
            if (0f == y){
                float max = this.maxY;
                float min = this.minY;
                y = (1f / (max - min));
                this.normY = y;
            }
            return y;
        }
    
        public String toString(){
            String string = this.string;
            if (null == string){
                StringBuilder strbuf = new StringBuilder();
                strbuf.append(this.getClass().getName());
                strbuf.append('[');
                strbuf.append(this.name());
                strbuf.append(',');
                strbuf.append(this.minX);
                strbuf.append(',');
                strbuf.append(this.minY);
                strbuf.append(',');
                strbuf.append(this.maxX);
                strbuf.append(',');
                strbuf.append(this.maxY);
                strbuf.append(']');
                string = strbuf.toString();
                this.string = string;
            }
            return string;
        }

    }


    public final Logitech.Pad pad ;

    public final Logitech.Button button ;

    public final Logitech.Stick stick ;

    protected Logitech(){
        super();
        this.pad = null;
        this.button = null;
        this.stick = null;
        this.event = 0;
    }
    protected Logitech(ByteBuffer buf){
        super();
        int stickLA = ReadShortBen(buf); 
        int stickRA = ReadShortBen(buf); 
        int buttonsA = ReadShortBen(buf); 
        int shiftA = ReadShortBen(buf); 
        int stickLB = ReadShortBen(buf); 
        int stickRB = ReadShortBen(buf); 
        int buttonsB = ReadShortBen(buf); 
        int shiftB = ReadShortBen(buf); 

        int event = 0;

        if (0x800 != buttonsA){
            this.pad = Pad.For(buttonsA);
            if (null != this.pad){
                this.button = null;
                event |= pad.rvalue;
            }
            else {
                this.button = Button.For(buttonsB);
                if (null != this.button)
                    event |= this.button.rvalue;
            }
        }
        else if (0x800 != buttonsB){
            this.pad = Pad.For(buttonsB);
            if (null != this.pad){
                this.button = null;
                event |= pad.rvalue;
            }
            else {
                this.button = Button.For(buttonsB);
                if (null != this.button)
                    event |= this.button.rvalue;
            }
        }
        else {
            this.button = null;
            this.pad = null;
        }

        if (null == this.pad && null == this.button){

            this.stick = new Stick(stickLA, stickLB, stickRA, stickRB);

            event |= this.stick.rvalue;
        }
        else {
            this.stick = null;
        }
        this.event = event;
    }

    public boolean isButton(){
        return (null != this.button);
    }
    public boolean isPad(){
        return (null != this.pad);
    }
    public boolean isStick(){
        return (null != this.stick);
    }

    /*
     * Stick data
     */

    public byte getMinX(){
        Stick s = this.stick;
        if (null != s)
            return s.minX;
        else
            return 0;
    }
    public byte getMinY(){
        Stick s = this.stick;
        if (null != s)
            return s.minY;
        else
            return 0;
    }
    public byte getMaxX(){
        Stick s = this.stick;
        if (null != s)
            return s.maxX;
        else
            return 0;
    }
    public byte getMaxY(){
        Stick s = this.stick;
        if (null != s)
            return s.maxY;
        else
            return 0;
    }
    public float getX(){
        Stick s = this.stick;
        if (null != s)
            return s.getX();
        else
            return 0f;
    }
    public float getY(){
        Stick s = this.stick;
        if (null != s)
            return s.getY();
        else
            return 0f;
    }
    

    /* *************************************************************************************
     *                                                                                     *
     *     Device Class                                                                    *
     *                                                                                     *

    public volatile static File DeviceFile = new File("/dev/usb/hid0"); //(doesnt work: permission denied)//

    static {
        String config = System.getProperty("fv3tk.hid.Logitech.DeviceFile");
        if (null != config && config.startsWith("/dev/") &&
            -1 == config.indexOf("/../"))
        {
            try {
                DeviceFile = new File(config).getCanonicalFile();
            }
            catch (IOException exc){
                String erm = String.format("%s: error configuring logitech joystick to '%s'",Thread.currentThread().getName(),config);
                throw new IllegalStateException(erm,exc);
            }
        }
        else if (null != config){
            String erm = String.format("%s: unable to configure logitech joystick to '%s'",Thread.currentThread().getName(),config);
            System.err.println(erm);
        }
    }

    private volatile static FileChannel DeviceChannel;


    protected final static int BlockSizeBytes = (8 * 4);
    /**
     * Prevent backlog for fast reads
     * /
    protected final static int RunReaderReadBlocks = 32;
    /**
     * Used by Run for this device class.
     * /
    private volatile static ByteBuffer RunReader;

    protected final static int RunReaderBlocks = RunReaderReadBlocks * BlockSizeBytes;

    /**
     * Called by classloader (clinit).
     * /
    public final static void Init(fv3tk.Fv3Hid hid){
        if (null != DeviceFile && DeviceFile.exists()){
            try {
                DeviceChannel = ChannelReadWrite(DeviceFile);

                System.err.println(String.format("%s: configured logitech joystick to '%s'",Thread.currentThread().getName(),DeviceFile.getPath()));
            }
            catch (IOException exc){
                String erm = String.format("%s: error configuring logitech joystick to '%s'",Thread.currentThread().getName(),DeviceFile.getPath());
                throw new IllegalStateException(erm,exc);
            }
        }
        else
            System.err.println(String.format("%s: logitech joystick not found at '%s'",Thread.currentThread().getName(),DeviceFile.getPath()));
    }
    public final static Fv3Hid.Device Run(long nanos){
        FileChannel ch = DeviceChannel;
        if (null != ch){
            ByteBuffer reader = RunReader;
            if (null == reader){
                reader = ByteBuffer.allocate(RunReaderBlocks);
                RunReader = reader;
            }
            else
                Clear(reader);
            try {
                int read = ch.read(reader);
                if (0 < read){
                    Fv3Hid.Device evt = new Logitech(reader);
                    try {
                        do {
                            evt = evt.insert(new Logitech(reader));
                        }
                        while (true);
                    }
                    catch (RuntimeException eof){
                    }
                    return evt;
                }
            }
            catch (RuntimeException exc){

                System.err.println(String.format("%s: error reading logitech joystick at '%s' (%s).",Thread.currentThread().getName(),DeviceFile.getPath(), exc.toString()));
            }
            catch (IOException exc){

                System.err.println(String.format("%s: error with logitech joystick at '%s' (%s).",Thread.currentThread().getName(),DeviceFile.getPath(), exc.toString()));

                Shutdown();
            }
        }
        return null;
    }
    public final static void Shutdown(){
        if (null != DeviceChannel){
            FileChannel ch = DeviceChannel;
            DeviceChannel = null;
            try {
                ch.close();
            }
            catch (Throwable any){
            }
            RunReader = null;
        }
    }

     ************************************************************************************* */
}
